using System.Collections.Generic;
using System.Drawing;
using Microsoft.VisualStudio.Modeling;
using DslDiagrams = Microsoft.VisualStudio.Modeling.Diagrams;

namespace Microsoft.Modeling.StoryboardDesigner
{
    partial class ScenarioShape
    {
        /// <summary>
        /// Constant that specifies minimum number of lines
        /// that will be displayed in the compartment.
        /// </summary>
        internal const int ActionCompartmentMinimumLines = 1;

        /// <summary>
        /// Constant that specifies whether the compartment will 
        /// initially be expanded.
        /// </summary>
        internal const bool ActionCompartmentDefaultExpanded = true;

        // Compartment names
        internal const string ActionCompartmentName = "actionCompartment";

        private static DslDiagrams::CompartmentDescription[] compartmentDescriptions;

        public override DslDiagrams::CompartmentDescription[] GetCompartmentDescriptions()
        {
            if(compartmentDescriptions == null)
            {
                // Initialize the array of compartment descriptions if we haven't done so already. 
                // First we get any compartment descriptions in base shapes, and add on any compartments
                // that are defined on this shape. 
                List<DslDiagrams::CompartmentDescription> descs = new List<Microsoft.VisualStudio.Modeling.Diagrams.CompartmentDescription>();
                descs.AddRange(base.GetCompartmentDescriptions());

                // Add a multi-line compartment for Action
                // Action will be used to carry script
                string title = "Script";
                descs.Add(new MultiLineCompartmentDescription(
                    ActionCompartmentName,
                    title,
                    Color.LightGray, 
                    false,
                    Color.FromKnownColor(KnownColor.Transparent), 
                    false,
                    null,
                    GetActionDisplayValue,
                    SetActionDisplayValue,
                    ActionCompartmentMinimumLines,
                    ActionCompartmentDefaultExpanded));

                // Copy the list to the static array
                compartmentDescriptions = descs.ToArray();
            }

            return compartmentDescriptions;
        }

        private static global::System.Collections.Generic.Dictionary<global::System.Type, DslDiagrams::CompartmentMapping[]> compartmentMappings;

        /// <summary>
        /// Gets an array of CompartmentMappings for all compartments displayed on this shape
        /// (including compartment maps defined on base shapes). 
        /// </summary>
        /// <param name="melType">The type of the DomainClass that this shape is mapped to</param>
        /// <returns></returns>
        protected override DslDiagrams::CompartmentMapping[] GetCompartmentMappings(global::System.Type melType)
        {
            if(melType == null) throw new global::System.ArgumentNullException("melType");

            if(compartmentMappings == null)
            {
                // Initialize the table of compartment mappings if we haven't done so already. 
                // The table contains an array of CompartmentMapping for every Type that this
                // shape can be mapped to. 
                compartmentMappings = 
                    new global::System.Collections.Generic.Dictionary<global::System.Type, DslDiagrams::CompartmentMapping[]>();

                // First we get the mappings defined for the base shape, and add on any mappings defined for this
                // shape. 
                DslDiagrams::CompartmentMapping[] baseMappings = 
                    base.GetCompartmentMappings(typeof(Scenario));
                
                int localCompartmentMappingsOffset = 0;

                if(baseMappings != null && baseMappings.Length > 0)
                {
                    localCompartmentMappingsOffset = baseMappings.Length;
                }

                DslDiagrams::CompartmentMapping[] newMappings = 
                    new DslDiagrams::CompartmentMapping[localCompartmentMappingsOffset + 1];

                if(baseMappings != null && baseMappings.Length > 0)
                {
                    baseMappings.CopyTo(newMappings, 0);
                }

                // Add a mapping for the multi-line compartment
                newMappings[localCompartmentMappingsOffset] = 
                    new MultiLineCompartmentMapping(ActionCompartmentName);

                compartmentMappings.Add(typeof(Scenario), newMappings);
            }

            // See if we can find the mapping being requested directly in the table. 
            DslDiagrams::CompartmentMapping[] returnValue;

            if(compartmentMappings.TryGetValue(melType, out returnValue))
            {
                return returnValue;
            }

            // If not, loop through the types in the table, and find the 'most derived' base
            // class of melType. 
            global::System.Type selectedMappedType = null;

            foreach(global::System.Type mappedType in compartmentMappings.Keys)
            {
                if(mappedType.IsAssignableFrom(melType) && 
                    (selectedMappedType == null || selectedMappedType.IsAssignableFrom(mappedType)))
                {
                    selectedMappedType = mappedType;
                }
            }

            if(selectedMappedType != null)
            {
                return compartmentMappings[selectedMappedType];
            }

            return new DslDiagrams::CompartmentMapping[] { };
        }

        #region Action compartment delegate implementations
        private static string GetActionDisplayValue(MultiLineCompartment compartment)
        {
            if(compartment != null && compartment.ParentShape != null)
            {
                Scenario mel = compartment.ParentShape.ModelElement as Scenario;

                if(mel != null)
                {
                    return mel.Action;
                }
            }

            return Properties.Resources.DefaultDisplayValue;
        }

        private static void SetActionDisplayValue(MultiLineCompartment compartment, object newValue)
        {
            // Don't try to update the  value if we aren't
            // in a transaction (could happen during "undo");
            if(compartment.Store.TransactionActive)
            {
                Scenario mel = compartment.ParentShape.ModelElement as Scenario;

                if(mel != null)
                {
                    mel.Action = newValue.ToString();
                }
            }
        }

        #endregion
    }
}